Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.

...powered by www.netzwerkartist.de...

Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Mathematisches
6 Eigene Klassen schreiben
7 Exceptions
8 Die Funktionsbibliothek
9 Threads und nebenläufige Programmierung
10 Raum und Zeit
11 Datenstrukturen und Algorithmen
12 Dateien und Datenströme
13 Die eXtensible Markup Language (XML)
14 Grafische Oberflächen mit Swing
15 Grafikprogrammierung
16 Das Netz
17 JavaServer Pages und Servlets
18 Verteilte Programmierung mit RMI und Web–Services
19 Applets, Midlets und Sound
20 Datenbankmanagement mit JDBC
21 Reflection und Annotationen
22 Komponenten durch Bohnen
23 Logging und Monitoring
24 Sicherheitskonzepte
25 Java Native Interface (JNI)
26 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Index

Download:
- ZIP, ca. 12,5 MB
Buch bestellen

Website zum Buch
Weblog des Autors
Ihre Meinung?

Spacer
 <<   zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren mit der Java Standard Edition Version 6
Buch: Java ist auch eine Insel

Java ist auch eine Insel
6., akt. und erw. Aufl., mit DVD
1.454 S., 49,90 Euro
Galileo Computing
ISBN 3-89842-838-9
gp 2 Sprachbeschreibung
  gp 2.1 Elemente der Programmiersprache Java
    gp 2.1.1 Textkodierung durch Unicode-Zeichen
    gp 2.1.2 Literale
    gp 2.1.3 Bezeichner
    gp 2.1.4 Reservierte Schlüsselwörter
    gp 2.1.5 Token
    gp 2.1.6 Kommentare
  gp 2.2 Anweisungen und Programme
    gp 2.2.1 Eine Klasse bildet den Rahmen
    gp 2.2.2 Die Reise beginnt am main()
    gp 2.2.3 Programme übersetzen und starten
    gp 2.2.4 Funktionsaufrufe als Ausdrücke und Anweisungen
    gp 2.2.5 Modifizierer
    gp 2.2.6 Anweisungen und Blöcke
  gp 2.3 Datentypen
    gp 2.3.1 Primitive Datentypen im Überblick
    gp 2.3.2 Wahrheitswerte
    gp 2.3.3 Variablendeklarationen
    gp 2.3.4 Ganzzahlige Datentypen
    gp 2.3.5 Die Fließkommazahlen float und double
    gp 2.3.6 Alphanumerische Zeichen
    gp 2.3.7 Die Typanpassung (das Casting)
  gp 2.4 Ausdrücke, Operanden und Operatoren
    gp 2.4.1 Zuweisungsoperator
    gp 2.4.2 Arithmetische Operatoren
    gp 2.4.3 Unäres Minus und Plus
    gp 2.4.4 Zuweisung mit Operation
    gp 2.4.5 Präfix- oder Postfix-Inkrement und -Dekrement
    gp 2.4.6 Die relationalen Operatoren und die Gleichheitsoperatoren
    gp 2.4.7 Logische Operatoren Und, Oder, Xor, Nicht
    gp 2.4.8 Rang der Operatoren in der Auswertungsreihenfolge
    gp 2.4.9 Überladenes Plus für Strings
    gp 2.4.10 Was C(++)-Programmierer vermissen könnten
  gp 2.5 Bedingte Anweisungen oder Fallunterscheidungen
    gp 2.5.1 Die if-Anweisung
    gp 2.5.2 Die Alternative mit einer if/else-Anweisung wählen
    gp 2.5.3 Die switch-Anweisung bietet die Alternative
  gp 2.6 Schleifen
    gp 2.6.1 Die while-Schleife
    gp 2.6.2 Schleifenbedingungen und Vergleiche mit ==
    gp 2.6.3 Die do/while-Schleife
    gp 2.6.4 Die for-Schleife
    gp 2.6.5 Ausbruch planen mit break und Wiedereinstieg mit continue
    gp 2.6.6 break und continue mit Sprungmarken
  gp 2.7 Methoden einer Klasse
    gp 2.7.1 Bestandteil einer Funktion
    gp 2.7.2 Beschreibungen in der Java-API
    gp 2.7.3 Aufruf einer Methode
    gp 2.7.4 Methoden ohne Parameter
    gp 2.7.5 Statische Funktionen (Klassenmethoden)
    gp 2.7.6 Parameter, Argument und Wertübergabe
    gp 2.7.7 Methoden vorzeitig mit return beenden
    gp 2.7.8 Nicht erreichbarer Quellcode bei Funktionen
    gp 2.7.9 Rückgabewerte
    gp 2.7.10 Methoden überladen
    gp 2.7.11 Vorgegebener Wert für nicht aufgeführte Argumente
    gp 2.7.12 Finale lokale Variablen
    gp 2.7.13 Rekursive Funktionen
    gp 2.7.14 Die Türme von Hanoi
  gp 2.8 Weitere Operatoren
    gp 2.8.1 Operationen auf Bit-Ebene
    gp 2.8.2 Die Verschiebeoperatoren
    gp 2.8.3 Ein Bit setzen, löschen, umdrehen und testen
    gp 2.8.4 Bit-Funktionen der Integer- und Long-Klasse
    gp 2.8.5 Der Bedingungsoperator
  gp 2.9 Einfache Benutzereingaben
  gp 2.10 Zum Weiterlesen


Galileo Computing

2.4 Ausdrücke, Operanden und Operatoren  downtop

Beginnen wir mit mathematischen Ausdrücken, um dann die Schreibweise in Java zu ermitteln. Eine mathematische Formel, etwa der Ausdruck -27 * 9, besteht aus Operanden (engl. operand) und Operatoren (engl. operator). Ein Operand ist eine Variable oder ein Literal. Im Fall einer Variablen wird der Wert aus der Variablen ausgelesen und mit ihm die Berechnung durchgeführt.


Beispiel Ein Ausdruck mit Zuweisungen:
int i = 12, j; 
j = i * 2;

Die Multiplikation berechnet das Produkt von 12 und 2 und speichert das Ergebnis in j ab. Von allen primitiven Variablen, die in dem Ausdruck vorkommen, wird also der Wert ausgelesen und in den Ausdruck eingesetzt. [Es gibt Programmiersprachen, in denen werden Wertoperationen besonders gekennzeichnet. So etwa in LOGO. Eine Wertoperation schreibt sich dort mit einem Doppelpunkt vor der Variablen, etwa :X + :Y. ]

Dies nennt sich auch Wertoperation, da der Wert der Variablen betrachtet wird und nicht ihr Speicherort oder gar ihr Variablenname.

Die Arten von Operatoren

Operatoren verknüpfen die Operanden. Ist ein Operator auf genau einem Operand definiert, so nennt er sich unärer Operator (oder einstelliger Operator). Das Minus (negatives Vorzeichen) vor einem Operand ist ein unärer Operator, da er für genau den folgenden Operanden gilt. Die üblichen Operatoren Plus, Minus, Mal und Division sind binäre (zweistellige) Operatoren. Es gibt auch einen Fragezeichenoperator für bedingte Ausdrücke, der dreistellig ist.

Ausdrücke

Ein Ausdruck (engl. expression) ergibt bei der Auswertung ein Ergebnis. Dieser Wert wird auch Resultat genannt. Ausdrücke haben immer einen Wert, während das für Anweisungen (wie eine Schleife) nicht gilt. Daher kann ein Ausdruck an allen Stellen stehen, an denen ein Wert benötigt wird. Dieser Wert ist entweder ein

  • numerischer Typ (von arithmetischen Ausdrücken) oder ein
  • Referenztyp (etwa von einer Objekt-Erzeugung).

Operatoren erlauben die Verbindung einzelner Ausdrücke zu neuen Ausdrücken. Einige Operatoren sind aus der Schule bekannt, wie Addition, Vergleich, Zuweisung und Weitere. C(++)-Programmierer werden viele Freunde wiedererkennen.


Galileo Computing

2.4.1 Zuweisungsoperator  downtop

Der Zuweisungsoperator kopiert den Wert eines Ausdrucks der rechten Seite in die Variable der linken Seite. In Java stellt das Gleichheitszeichen = eine Zuweisung (engl. assignment) mit dar. Der linke Teil einer Zuweisung ist eine Variable, die den Wert speichert, und nennt sich l-Wert. Der rechte Teil ist der Ausdruck, der ausgewertet in die Variable geschrieben wird. Er nennt sich r-Wert.

int a; 
a = 12 * 3;

Die Zuweisungen sehen zwar so aus wie mathematische Gleichungen, doch existiert ein wichtiger Unterschied: Die Formel a = a + 1 ist – zumindest im Dezimalsystem ohne zusätzliche Algebra – mathematisch nicht zu erfüllen, da es kein a geben kann, das a = a + 1 erfüllt. Aus Programmiersicht ist es in Ordnung, da die Variable a um eins erhöht wird. In anderen Programmiersprachen wird die Zuweisung durch ein anderes Symbol deutlich gemacht (etwa :=); Java folgt hier der Tradition von C(++) und definiert einen binären Vergleichsoperator mit ==.


Hinweis Eine Zuweisung ist insbesondere ein Ausdruck, der einen Wert liefert. Zwar finden sich Zuweisungen oft als Anweisungen wieder, doch sie können an jeder Stelle stehen, an der ein Ausdruck erlaubt ist, etwa in einem Funktionsaufruf wie print():
int a; 
a = 18;                           // Anweisung mit Zuweisung 
System.out.println( a = 19 );     // Ausdruck mit Zuweisung. Liefert 19 
System.out.println( a == 19 );    // Ausdruck mit Vergleich. Liefert true

Mehrere Zuweisungen in einer Anweisung

Zuweisungen der Form a = b = 0 sind erlaubt und gleichbedeutend mit zwei Anweisungen b = 0 und a = b beziehungsweise mit expliziter Klammerung a = (b = 0). Daran lässt sich ablesen, dass beim Zuweisungsoperator die Auswertung von rechts nach links erfolgt. Doch wenn wir meinen, dass

a = (b = c + d) + e;

eine Vereinfachung im Vergleich zu

b = c + d; 
a = b + e;

ist, sollten wir mit einer Zuweisung pro Zeile auskommen.


Galileo Computing

2.4.2 Arithmetische Operatoren  downtop

Ein arithmetischer Operator verknüpft die Operanden mit den Operatoren Addition (+), Subtraktion (-), Multiplikation (*) und Division (/). Zusätzlich gibt es den Restwert-Operator %, der den bei der Division verbleibenden Rest betrachtet. Alle Operatoren sind für ganzzahlige Werte sowie für Fließkommazahlen definiert. [In C sind sie nur für Ganzzahlen definiert. ] Die arithmetischen Operatoren sind binär, und auf der linken und rechten Seite sind die Typen numerisch. Der Ergebnistyp ist ebenfalls numerisch.

Numerische Umwandlung

Bei unterschiedlichen Datentypgrößen bringt der Compiler vor der Anwendung der Operation alle Operanden auf den größten vorkommenden Typ. Das nennt sich numerische Umwandlung (engl. numeric promotion). Bei byte und short gilt die Sonderregelung, dass sie vorher auf int konvertiert werden. [http://java.sun.com/docs/books/jls/third_edition/html/conversions.html#26917 ] (Auch im Java Bytecode gibt es keine arithmetischen Operationen auf byte, short und char.) Anschließend wird die Operation ausgeführt, und der Ergebnistyp entspricht dem umfassenderen Typ.

Der Divisionsoperator

Der binäre Operator »/« bildet den Quotienten aus Dividend und Divisor. Auf der linken Seite steht der Dividend und auf der rechten der Divisor. Die Division ist für Ganzzahlen und für Fließkommazahlen definiert. Bei der Ganzzahldivision wird zu null hin gerundet und das Ergebnis ist keine Fließkommazahl, so dass 1/3 das Ergebnis 0 ergibt, und nicht 0,333. Den Datentyp des Ergebnisses bestimmen die Operanden und nicht der Operator. Soll das Ergebnis vom Typ double sein, muss ein Operand ebenfalls double sein.

System.out.println( 1.0 / 3 );          // 0.3333333333333333 
System.out.println( 1   / 3.0 );        // 0.3333333333333333 
System.out.println( 1 / 3 );            // 0

Schon in der Schulmathematik war die Division durch null nicht definiert. Führen wir eine Ganzzahldivision mit dem Divisor 0 durch, so bestraft uns Java mit einer ArithmeticException. Bei Fließkommazahlen verläuft dies anders. Eine Division durch 0 liefert +/– unendlich und ein NaN bei 0.0/0.0. Ein NaN steht für Not-A-Number und wird vom Prozessor erzeugt, falls er eine mathematische Operation wie die Division durch null nicht durchführen kann. In Kapitel 5 werden wir auf NaN noch einmal zurückkommen.

Der Restwert-Operator %

Eine Ganzzahldivision muss nicht unbedingt glatt aufgehen, wie im Fall 9/2. In diesem Fall gibt es den Rest 1. Diesen Rest liefert der Restwert-Operator (engl. remainder operator), oft auch Modulo genannt. (Mathematiker unterscheiden die beiden Begriffe Rest und Modulo, da ein Modulo nicht negativ ist, der Rest in Java aber schon. Das soll uns aber egal sein.)

System.out.println( 9% 2 );            // 1

Im Gegensatz zu C(++) [Wir müssten in C(++) die Funktion fmod() benutzen. ] ist der Restwert-Operator in Java auch auf Fließkommazahlen anwendbar, und die Operanden können negativ sein. [Die Sprachdefinition von C(++) schreibt bei der Division und beim Modulo mit negativen Zahlen keine Berechnungsmethode vor. ]

System.out.println( 12. % 2.5 );       // 2.0

Die Division und der Restwert richten sich in Java nach einer einfachen Formel: int(a/b)*b + (a%b) = a.


Beispiel Die Gleichung ist erfüllt, wenn wir etwa a = 10 und b = 3 wählen. Es gilt: int(10/3) = 3 und 10  % 3 ergibt 1. Dann ergeben 3 * 3 + 1 = 10.

Aus dieser Gleichung folgt, dass beim Restwert das Ergebnis nur dann negativ ist, wenn der Dividend negativ ist; er ist nur dann positiv, wenn der Dividend positiv ist. Es ist leicht einzusehen, dass das Ergebnis der Restwert-Operation immer echt kleiner ist als der Wert des Divisors. Wir haben den gleichen Fall wie bei der Ganzzahldivision, dass ein Divisor mit dem Wert 0 eine ArithmeticException auslöst und bei Fließkommazahlen zum Ergebnis NaN führt.


Beispiel Unterschiedliche Vorzeichen beim Restwert-Operator:

Listing 2.6    RemainerAndDivDemo.java, main()

System.out.println( " 5%  3 = " + ( 5%  3) );   //  2 
System.out.println( " 5 /  3 = " + ( 5 /  3) ); //  1 
 
System.out.println( " 5% -3 = " + ( 5% -3) );   //  2 
System.out.println( " 5 / -3 = " + ( 5 / -3) );  // -1 
 
System.out.println( "-5%  3 = " + (-5%  3) );    // -2 
System.out.println( "-5 /  3 = " + (-5 /  3) );  // -1 
 
System.out.println( "-5% -3 = " + (-5% -3) );    // -2 
System.out.println( "-5 / -3 = " + (-5 / -3) );  //  1

Gewöhnungsbedürftig ist die Tatsache, dass der zweite Operand das Vorzeichen des Restes definiert und niemals der erste.


Restwert für Fließkommazahlen und IEEEremainder()

Über die oben genannte Formel können wir auch bei Fließkommazahlen das Ergebnis einer Restwert-Operation leicht berechnen. Dabei muss beachtet werden, dass sich der Operator nicht so wie unter IEEE 754 verhält. Denn diese Norm schreibt vor, dass die Restwert-Operation den Rest von einer rundenden Division berechnet und nicht von einer abschneidenden. So wäre das Verhalten nicht analog zum Restwert bei Ganzzahlen. Java definiert den Restwert jedoch bei Fließkommazahlen genauso wie den Restwert bei Ganzzahlen. Wünschen wir ein Restwert-Verhalten, wie es IEEE 754 vorschreibt, so können wir immer noch die Bibliotheksfunktion Math.IEEEremainder() verwenden.

Auch bei der Restwert-Operation bei Fließkommazahlen werden wir niemals eine Exception erwarten. Eventuelle Fehler werden, wie im IEEE-Standard beschrieben, mit NaN angegeben. Ein Überlauf oder Unterlauf kann zwar passieren, aber nicht geprüft werden.

Rundungsfehler

Prinzipiell sollten Anweisungen wie 1.1 – 0.1 immer 1.0 ergeben, jedoch treten interne Rundungsfehler bei der Darstellung auf und lassen das Ergebnis von Berechnung zu Berechnung immer ungenauer werden. Ein besonders ungünstiger Fehler trat 1994 beim Pentium-Prozessor im Divisionsalgorithmus Radix-4 SRT auf, ohne dass der Programmierer der Schuldige war.

double x, y, z; 
x = 4195835.0; 
y = 3145727.0; 
z = x - (x/y) * y; 
System.out.println( z );

Ein fehlerhafter Prozessor liefert hier 256, obwohl laut Rechenregel das Ergebnis 0 sein muss. Laut Intel sollte für einen normalen Benutzer (Spieler, Softwareentwickler, Surfer?) der Fehler nur alle 27 000 Jahre auftauchen. Glück für die meisten. Eine Studie von IBM errechnete eine Fehlerhäufigkeit von einmal in 24 Tagen. Alles in allem hat Intel die CPUs zurückgenommen, über 400 Millionen US-Dollar verloren und spät den Kopf gerade noch aus der Schlinge gezogen.

Die meisten Rundungsfehler resultieren aber daher, dass endliche Dezimalbrüche im Rechner als Näherungswerte für periodische Binärbrüche repräsentiert werden müssen. 0,1 entspricht einer periodischen Mantisse im IEEE-Format.


Galileo Computing

2.4.3 Unäres Minus und Plus  downtop

Die binären Operatoren sitzen zwischen zwei Operanden, während sich ein unärer Operator genau einen Operanden vornimmt. Das unäre Minus (Operator zur Vorzeichenumkehr) etwa dreht das Vorzeichen des Operanden um. So wird aus einem positiven Wert ein negativer und aus einem negativen ein positiver.


Beispiel Drehe das Vorzeichen einer Zahl um:
a = -a;

Alternativ ist:

a = -1 * a;

Das unäre Plus ist eigentlich unnötig; die Entwickler haben es jedoch aus Symmetriegründen mit eingeführt.


Beispiel Minus und Plus sitzen direkt vor dem Operanden, und der Compiler weiß selbstständig, ob dies unär oder binär ist. Der Compiler erkennt auch folgende Konstruktion:
int i = - - - 2 + - + 3;

Dies ergibt den Wert -5. Einen Ausdruck wie ---2+-+3 erkennt der Compiler dagegen nicht an, da die zusammenhängenden Minuszeichen als Inkrement interpretiert werden und nicht als unärer Operator. Das Leerzeichen ist also bedeutend.


Vorzeichen erfragen

Um für das Vorzeichen einen Wert +1 für positive oder –1 für negative Zahlen und 0 für 0 zu bekommen, lässt sich die Funktion signum() verwenden. Sie ist nicht ganz logisch auf die Klasse Math für Fließkommazahlen und Integer/Long für Ganzzahlen verteilt:

  • java.lang.Integer.signum( int i )
  • java.lang.Long.signum( long i )
  • java.lang.Math.signum( double d )
  • java.lang.Math.signum( float f )

Galileo Computing

2.4.4 Zuweisung mit Operation  downtop

In Java lassen sich Zuweisungen mit numerischen Operatoren kombinieren. Für einen Operator # im Ausdruck a = a # (b) kürzt der Verbundoperator den Ausdruck zu a #= b ab. So addiert a += 2 zur Variable a 2 hinzu, und der Rückgabewert ist die um 2 erhöhte Variable a.

Besondere Obacht sollten wir auf die automatische Klammerung geben. Bei einem Ausdruck wie a *= 3 + 5 gilt a = a * (3 + 5) und nicht selbstverständliche die Punkt-vor-Strich-Regelung a = a * 3 + 5.

Falls es sich bei der rechten Seite um einen komplexeren Ausdruck handelt, wird dieser nur einmal ausgewertet. Dies ist wichtig bei Methodenaufrufen, die Nebenwirkungen besitzen, etwa Zustände wie einen Zähler verändern.


Beispiel Wir profitieren auch bei einem Feldzugriff (siehe Abschnitt 3.9, »Arrays«) von Verbundoperationen, da der Zugriff auf das Feldelement nur einmal stattfindet:
feld[ 2 * i + j ] = feld[ 2 * i + j ] + 1;
Leichter zu lesen ist die folgende Anweisung:
feld[ 2 * i + j ] += 1;

Typanpassung beim Verbundoperator

Beim Verbundoperator wird noch etwas mehr gemacht, als E1 #= E2 zu E1 = (E1) # (E2) aufzulösen. Interessanterweise kommt auch noch der Typ von E1 ins Spiel, denn der Ausdruck E1 # E2 wird vor der Zuweisung auf den Datentyp von E1 gebracht, sodass es genau heißen muss: E1 #= E2 wird zu E1 = (Typ von E1)((E1) # (E2)).


Beispiel Auf eine Ganzzahl soll der Verbundoperator eine Fließkommazahl addieren.
int i = 1973; 
i += 30.2;

Die Anwendung des Verbundoperators ist in Ordnung, denn der Übersetzer nimmt eine implizite Typanpassung vor, sodass die Bedeutung bei i = (int)(i + 30.2) liegt. So viel dazu, dass Java eine intuitive und einfache Programmiersprache sein soll.



Galileo Computing

2.4.5 Präfix- oder Postfix-Inkrement und -Dekrement  downtop

Das Herauf- und Heruntersetzen von Variablen ist eine sehr häufige Operation, wofür die Entwickler in der Vorgängersprache C auch einen Operator spendiert hatten. Die praktischen Operatoren ++ und -- kürzen die Programmzeilen zum Inkrement und Dekrement ab.

i++;     // Abkürzung für i = i + 1 
j--;     //               j = j - 1

Eine lokale Variable muss allerdings vorher initialisiert sein, da ein Lesezugriff vor einem Schreibzugriff stattfindet.

Vorher oder nachher

Die beiden Operatoren liefern einen Ausdruck und geben daher einen Wert zurück. Es macht jedoch einen feinen Unterschied, wo dieser Operator platziert wird. Es gibt ihn nämlich in zwei Varianten: vor der Variable und dahinter. Steht das Inkrement vor der Variable, sprechen wir von Prä-Inkrement/Prä-Dekrement, steht es dahinter, von Post-Inkrement/Post-Dekrement – kurz auch Präfix/Postfix genannt. Nutzen wir einen Präfix-Operator, so wird die Variable erst herauf- beziehungsweise heruntergesetzt und dann der Wert geliefert. Neben der Wertrückgabe gibt es eine Veränderung der Variable.


Beispiel Präfix/Postfix in einer Ausgabeanweisung:
int i = 10, j = 20; 
System.out.println( ++i );       // 11 
System.out.println( --j );       // 19 
System.out.println( i );         // 11 
System.out.println( j );         // 19

Die Variable wird erst heraufgesetzt und dann ausgegeben. Wiederholen wir mit i und j die Ausgaben mit Postfix:

int i = 10, j = 20; 
System.out.println( i++ );       // 10 
System.out.println( j-- );       // 20 
System.out.println( i );         // 11 
System.out.println( j );         // 19

Der Wert wird im Ausdruck verwendet und erst anschließend heraufgesetzt. Wir bekommen mit dem Präfix den Ausdruck nach der Operation und mit dem Postfix den Ausdruck davor.


Das Post-Inkrement finden wir auch im Namen der Programmiersprache C++. Es soll ausdrücken, dass es C-mit-eins-drauf ist, also ein verbessertes C. Mit dem Wissen über den Postfix-Operator ist klar, dass wir erst einen Zugriff haben und dann die Erhöhung stattfindet – also C++ ist auch nur C, und der Vorteil kommt später. (Einer der Entwickler von Java, Bill Joy, hat einmal Java als C++-- [--C++ könnte besser passen: Erst wird C++ bereinigt und dann zu Java erweitert. ] beschrieben. Er meinte damit C++ ohne die schwer zu pflegenden Eigenschaften.) Bei den von Microsoft geschaffenen Buchstabe-#-Sprachen – das # liest sich »Sharp« – wurde die Idee von den Musiknoten entlehnt. Ein Sharp hinter einer Note bedeutet, dass sie um einen Halbton erhöht wird. Das stimmt: Insbesondere C# nähert sich nur in Halbtonschritten.


Beispiel In Java sind Inkrement (++) und Dekrement (--) für alle numerischen Datentypen erlaubt, also auch für Fließkommazahlen.
double d = 12; 
System.out.println( --d );              // 11.0 
double e = 12.456; 
System.out.println( --e );              // 11.456

Einige Kuriositäten

Wir wollen uns abschließend noch mit einer Besonderheit des Post-Inkrements und Prä-Inkrements beschäftigen, die nicht nachahmenswert ist:

a = 2; 
a = ++a;      // a = 3 
b = 2; 
b = b++;      // b = 2

Im ersten Fall bekommen wir den Wert 3 und im zweiten Fall 2. Der erste Fall überrascht nicht. Denn a = ++a erhöht den Wert 2 um 1, und anschließend wird 3 der Variablen a zugewiesen. Bei b ist es raffinierter. Der Wert von b ist 2, und dieser Wert wird intern vermerkt. Anschließend erhöht b++ die Variable b. Doch die Zuweisung setzt b auf den gemerkten Wert, der 2 war. Also ist b = 2.


Galileo Computing

2.4.6 Die relationalen Operatoren und die Gleichheitsoperatoren  downtop

Relationale Operatoren sind Vergleichsoperatoren, die Ausdrücke miteinander vergleichen und einen Wahrheitswert vom Typ boolean zurückgeben. Die von Java zur Verfügung gestellten Operatoren sind:

  • Größer (>)
  • Kleiner (<)
  • Größer-gleich (>=), Kleiner-gleich (<=)
  • für numerische Vergleiche sowie ein Spezial-Operator
  • instanceof zum Testen von Referenzeigenschaften

Zudem kommen zwei Vergleichsoperatoren hinzu, die Java als Gleichheitsoperatoren bezeichnet:

  • Test auf Gleichheit (==)
  • Test auf Ungleichheit (!=)

Dass Java hier einen Unterschied macht, liegt an einem etwas anderen Vorrang, der uns aber nicht weiter beschäftigen soll.


Hinweis Bei einem Gleichheitsvergleich zwischen Variable und Literal schreiben viele Entwickler die Variable gerne links und die Konstante rechts, etwa in folgender Fallunterscheidung:
if ( anzahlZentnerGoldes == 666 )

Beide Operanden können aber vertauscht werden, was am Ergebnis nichts ändert, denn der Vergleichsoperator ist kommutativ, wenn denn die beiden Seiten keine beeinflussenden Seiteneffekte produzieren, also etwa Zustände ändern.

if ( 666 == anzahlZentnerGoldes )

Ebenso wie arithmetische Operatoren passen die relationalen Operatoren ihre Operanden an einen gemeinsamen Typ an. Handelt es sich bei den Typen um Referenztypen, so sind nur die Vergleichsoperatoren == und != erlaubt.

Kaum Verwechslungsprobleme durch == und =

Die Verwendung des relationalen Operators == und der Zuweisung = führt bei Einsteigern oft zu Problemen, da die Mathematik für Vergleiche und Zuweisungen immer nur ein Gleichheitszeichen kennt. Glücklicherweise ist das Problem in Java nicht so drastisch wie beispielsweise in C(++), da die Typen der Operatoren unterschiedlich sind. Der Vergleichsoperator ergibt immer nur den Rückgabewert boolean. Zuweisungen von numerischen Typen ergeben jedoch wieder einen numerischen Typ. Es kann also kein Problem wie das folgende geben:

int a = 10, b = 11; 
boolean result1 = ( a = b );        // Compilerfehler 
boolean result2 = ( a == b );       // Das ist OK

Beispiel Die Wahrheitsvariable hatVorzeichen soll dann true sein, wenn das Zeichen vorzeichen gleich dem Minus ist.
boolean hatVorzeichen = (vorzeichen == '-');

Schön ist die Auswertungsreihenfolge zu sehen: Erst wird das Ergebnis des Vergleichs berechnet, und dieser Wahrheitswert wird anschließend in hatVorzeichen kopiert.



Galileo Computing

2.4.7 Logische Operatoren Und, Oder, Xor, Nicht  downtop

Mit logischen Operatoren werden Wahrheitswerte nach definierten Mustern verknüpft. Logische Operatoren operieren nur auf boolean-Typen, andere Typen führen zu Compilerfehlern. Java bietet die Operatoren Und (&&), Oder (||), Xor (^) und Nicht (!) an. Xor ist eine Operation, die genau dann falsch zurückgibt, wenn entweder beide Operatoren wahr oder beide falsch sind. Sind sie unterschiedlich, so ist das Ergebnis wahr. Xor heißt auch exklusives beziehungsweise ausschließendes Oder.

Kurzschlussoperatoren

Eine Besonderheit sind die beiden Operatoren && (Und) beziehungsweise || (Oder). In der Regel muss ein logischer Ausdruck nur dann weiter ausgewertet werden, wenn er das Schlussergebnis noch beeinflussen kann. Zwei Operatoren bieten sich zur Optimierung der Ausdrücke an:

  • Und: Ist einer der beiden Ausdrücke falsch, so kann der Ausdruck schon nicht mehr wahr werden. Das Ergebnis ist falsch.
  • Oder: Ist mindestens einer der Ausdrücke schon wahr, so ist auch der gesamte Ausdruck wahr.

Der Compiler bzw. die Laufzeitumgeung kann den Programme abkürzen. Daher nennen sich die beiden Operatoren auch Kurzschlussoperatoren (engl. short-circuit-operator). [Den Begriff verwendet die Java Sprachdefinition nicht! Sie dazu auch http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.23. ]

Nicht-Kurzschlussoperatoren

In einigen Fällen ist erwünscht, dass die Laufzeitumgebung alle Teilausdrücke auswertet. Das kann der Fall sein, wenn Funktionen Nebenwirkungen haben sollen, etwa Zustände ändern. Daher bietet Java zusätzliche die nicht über einen Kurzschluss arbeitenden Operatoren | und & an, die eine Auswertung aller Teilausdrücke erzwingen.


Beispiel In dem ersten Ausdruck wird die Methode foo() nicht aufgerufen, im zweiten schon.
boolean b1 = true || foo();   // foo() wird nicht aufgerufen 
boolean b2 = false & foo();   // foo() wird aufgerufen

Für Xor kann es keinen Kurzschlussoperator geben, da immer beide Operanden ausgewertet werden müssen, bevor das Ergebnis feststeht.


Galileo Computing

2.4.8 Rang der Operatoren in der Auswertungsreihenfolge  downtop

Aus der Schule ist der Spruch »Punktrechnung geht vor Strichrechnung« bekannt, sodass sich der Ausdruck 1 + 2 * 3 zu 7 und nicht zu 9 auswertet. [Dass von diesen Rechnungen eine gewisse Spannung ausgeht, zeigen diverse Fernsehkanäle, die damit ihr Abendprogramm füllen. ]


Beispiel Zur Umwandlung einer Temperatur von Fahrenheit in Celsius zieht man von dem Wert in Fahrenheit 32 ab und multipliziert das Ergebnis mit 5/9:
celsius = fahrenheit - 32 * 5 / 9;

Die Umsetzung ist falsch, denn die Laufzeitumgebung zieht 32 * 5/9, also 17, von Fahrenheit ab, was keine gültige Umrechnung ist. Richtig ist Folgendes:

celsius = ( fahrenheit - 32 ) * 5 / 9;

In den meisten Programmiersprachen gibt es eine Unzahl von Operatoren neben Plus und Mal, die alle ihre eigenen Vorrangregeln besitzen. [Es gibt Programmiersprachen wie APL, die keine Vorrangregeln kennen. Sie werten die Ausdrücke streng von rechts nach links oder umgekehrt aus. ] Der Multiplikationsoperator besitzt zum Beispiel eine höhere Priorität und damit eine andere Auswertungsreihenfolge als der Plus-Operator.

Die Rechenregeln für Mal vor Plus kann sich jeder noch leicht merken. Auch ist leicht zu merken, dass die typischen arithmetischen Operatoren wie Plus und Mal eine höhere Priorität als Vergleichsoperationen haben. Komplizierter ist die Auswertung bei den zahlreichen Operatoren, die seltener im Programm vorkommen.


Beispiel Wie ist die Auswertung bei dem nächsten Ausdruck?
boolean A = false, 
        B = false, 
        C = true; 
System.out.println( A && B || C );

Gilt, dass entweder A && B oder C wahr sein müssen oder etwa A und B || C? Das Ergebnis fällt unterschiedlich aus. Entweder ist es true oder false.

Für derlei Feinheiten gibt es zwei Lösungen: entweder in einer Tabelle mit Vorrangregeln nachschlagen – §15.7 der JLS – oder auf diese Ungenauigkeiten verzichten.


Tabelle 2.8    Operatoren mit Rangordnung in Java
Operator Rang Typ Beschreibung
++, -- 1 arithmetisch Inkrement und Dekrement
+, - 1 arithmetisch unäres Plus und Minus
~ 1 integral bitweises Komplement
! 1 boolean logisches Komplement
(Typ) 1 jeder Cast
*, /, % 2 arithmetisch Multiplikation, Division, Rest
+, - 3 arithmetisch Addition und Subtraktion
+ 3 String String-Konkatenation
<< 4 integral Shift links
>> 4 integral Shift rechts mit Vorzeichenerweiterung
>>> 4 integral Shift rechts ohne Vorzeichenerweiterung
<, <=, >, >= 5 arithmetisch numerische Vergleiche
instanceof 5 Objekt Typvergleich
==, != 6 primitiv Gleich-/Ungleichheit von Werten
==, != 6 Objekt Gleich-/Ungleichheit von Referenzen
& 7 integral bitweises Und
& 7 boolean logisches Und
^ 8 integral bitweises Xor
^ 8 boolean logisches Xor
| 9 integral bitweises Oder
| 9 boolean logisches Oder
&& 10 boolean logisches konditionales Und, Kurzschluss
|| 11 boolean logisches konditionales Oder, Kurzschluss
?: 12 alles Bedingungsoperator
= 13 jede Zuweisung
*=, /=, %=, +=, -=, <<=, >>=, >>>=, &=, ^=, |= 14 jede Zuweisung mit Operation

Die Tabelle lehrt uns, dass im Beispiel A && B || C das Und stärker als das Oder bindet, also der Wert mit der Belegung A=false, B=false, C=true zu true ausgewertet wird. Vermutlich gibt es Programmierer, die dies wissen oder eine Tabelle mit Rangordnungen immer am Monitor kleben haben. Aber beim Durchlesen von fremdem Code ist es nicht schön, immer wieder die Tabelle konsultieren zu müssen, die verrät, ob nun das binäre Xor oder das binäre Und stärker bindet.


Tipp Alle Ausdrücke, die über die einfache Regel »Punktrechnung geht vor Strichrechnung« hinausgehen, sollten geklammert werden. Da die unären Operatoren ebenfalls sehr stark binden, kann eine Klammerung wegfallen.


Beispiel Bei den Operatoren + und * gilt die mathematische Kommutativität und Assoziativität. Das heißt, die Operanden können prinzipiell umgestellt werden, und das Ergebnis sollte davon nicht beeinträchtigt sein. Bei der Division gilt das nicht.
A / B / C

Der Ausdruck wird von links nach rechts ausgewertet, und zwar als (A / B) / C. Hier sind Klammern angemessen. Denn würde der Compiler den Ausdruck zu A / (B / C) auswerten, käme es einem A * C / B gleich.


Die mathematische Assoziativität gilt bei Gleitkommazahlen natürlich nicht, da diese nicht ohne Rechenfehler ablaufen. Daher gilt eine Auswertung von links nach rechts.


Galileo Computing

2.4.9 Überladenes Plus für Strings  downtop

Obwohl sich in Java die Operatoren fast alle auf primitive Datentypen beziehen, gibt es doch eine bemerkenswerte Verwendung des Plus-Operators. Diese wurde in Java eingeführt, da ein Aneinanderhängen von Zeichenketten oft benötigt wird. Objekte vom Typ String können durch den Plus-Operator mit anderen Strings und Datentypen verbunden werden. Falls zusammenhängende Teile nicht alle den Datentyp String annehmen, werden sie automatisch in einen String umgewandelt.

"Sandmännchen wünscht " + 12

ergibt den einen String »Sandmännchen wünscht 12«.

Besteht der Ausdruck aus mehreren Teilen, so muss die Auswertungsreihenfolge beachtet werden, andernfalls kommt es zu seltsamen Zusammensetzungen. So ergibt "Aufruf von " + 1 + 0 + 0 + " Ökonomen" tatsächlich »Aufruf von 100 Ökonomen« und nicht »Aufruf von 1 Ökonomen«, da der Compiler die Konvertierung in Strings dann startet, wenn der den Ausdruck als String-Objekt erkannt hat.


Beispiel Auswertungsreihenfolge vom Plus:

Listing 2.7    PlusString.java, main()

System.out.println( 1 + 2 );       // 3 
System.out.println( "1" + 2 );     // 12 
System.out.println( 1 + "2" );     // 12 
System.out.println( 1 + "2" + 3 ); // 123 
System.out.println( "1" + 2 + 3 ); // 123 
System.out.println( 1 + 2 + "3" ); // 33 
System.out.println( '1' + 2 );     // 51 (ASCII-Wert von '1' ist 49) 
System.out.println( 1 + '2' );     // 51


Beispiel Der Plus-Operator für Zeichenketten geht streng von links nach rechts und bereitet mit eingebetteten arithmetischen Ausdrücken mitunter Probleme. Eine Klammerung hilft, wie im Folgenden zu sehen ist:
"Ist 1 größer als 2? " + (1 > 2 ? "nein" : "ja");

Wäre der Ausdruck um den Bedingungsoperator nicht geklammert, dann würde der Plus-Operator an die Zeichenkette die 1 anhängen und es käme der >-Operator. Der erwartet aber kompatible Datentypen, die in unserem Fall – links stünde die Zeichenkette und rechts ein int – nicht gegeben sind.



Galileo Computing

2.4.10 Was C(++)-Programmierer vermissen könnten  toptop

Da es in Java keine Pointer-Operationen gibt, existiert das Operatorzeichen zur Referenzierung (&) und Dereferenzierung (*) nicht. Ebenso ist ein sizeof unnötig, da das Laufzeitsystem und der Compiler immer die Größe von Klassen kennen beziehungsweise die primitiven Datentypen immer eine feste Länge haben. Eine abgeschwächte Version vom Kommaoperator ist in Java nur im Kopf von for-Schleifen erlaubt.



Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.





 <<   zurück



Copyright © Galileo Press 2007
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de